﻿using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json;
using System.Net;
using System.Net.Sockets;
using System.Text;
 
using 并发处理.Datas;

namespace 并发处理.Filter
{
    // prevents the action filter methods to be invoked twice
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
    public class ETagFilter : ActionFilterAttribute, IAsyncActionFilter
    {
        public override async Task OnActionExecutionAsync(ActionExecutingContext executingContext,ActionExecutionDelegate next)
        {
            executingContext.HttpContext.Request.EnableBuffering();
            var request = executingContext.HttpContext.Request;

            // Computing and Validating ETags for Edit Collisions on POST/PUT requests
            if (!request.Method.Equals(HttpMethod.Get) && request.Headers.ContainsKey(HeaderNames.IfMatch)
            )
            {
                if (!await ValidateETagForMidAirEditsCollision(executingContext))
                {
                    return;
                }
            }

            var executedContext = await next();

            var response = executedContext.HttpContext.Response;

            // Computing ETags for Response Caching on GET requests
            if (
                request.Method == HttpMethod.Get.Method
                && response.StatusCode == (int)HttpStatusCode.OK
            )
            {
                ValidateETagForResponseCaching(executedContext);
            }
        }

        private void ValidateETagForResponseCaching(ActionExecutedContext executedContext)
        {
            if (executedContext.Result == null)
            {
                return;
            }

            var request = executedContext.HttpContext.Request;
            var response = executedContext.HttpContext.Response;

            var result = (BaseEntity)(executedContext.Result as ObjectResult).Value;

            // generate ETag from LastModified property
            var etag = GenerateEtagFromLastModified(result.LastModify);

            // generates ETag from the entire response Content
            //var etag = GenerateEtagFromResponseBodyWithHash(result);

            if (request.Headers.ContainsKey(HeaderNames.IfNoneMatch))
            {
                // fetch etag from the incoming request header
                var incomingEtag = request.Headers[HeaderNames.IfNoneMatch].ToString();

                // if both the etags are equal
                // raise a 304 Not Modified Response
                if (incomingEtag.Equals(etag))
                {
                    executedContext.Result = new StatusCodeResult((int)HttpStatusCode.NotModified);
                }
            }

            // add ETag response header 
            response.Headers.Add(HeaderNames.ETag,   etag  );
        }

        private async Task<bool> ValidateETagForMidAirEditsCollision(ActionExecutingContext executingContext)
        {
            var request = executingContext.HttpContext.Request;
            var incomingETag = request.Headers[HeaderNames.IfMatch];

            executingContext.HttpContext.Request.EnableBuffering();
            executingContext.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);
            string body;
            using (var reader = new StreamReader(executingContext.HttpContext.Request.Body, Encoding.UTF8))
            {
                body = await reader.ReadToEndAsync();
            }
          //  executingContext.HttpContext.Request.Body.Position = 0;

            BaseEntity entity =  JsonConvert.DeserializeObject<BaseEntity>(body);
             
            if (entity!=null)
            {
                var result =   CompanyRepository.FindById(Convert.ToInt32(entity.Id));
                if (result != null)
                {
                    // generates ETag from the entire response Content
                    var etag = GenerateEtagFromLastModified(result.LastModify);

                    // if both the etags are not equal
                    // the data has already changed on the server side
                    // mid-air collision
                    // respond with a 412
                    if (!incomingETag.Equals(etag))
                    {
                        executingContext.Result = new StatusCodeResult(
                                (int)HttpStatusCode.PreconditionFailed);

                        return false;
                    }
                }
            }

            return true;
        }

        private string   GenerateEtagFromLastModified(DateTime lastModified)
        {
            return lastModified.Ticks.ToString();
        }
    }
}
